/**
 * FileSync - Classe principal da aplicação
 * Classe que implementa a idéia do trabalho proposto para a disciplina de GDD.
 * Proposta - Sincronização de arquivos por P2P utilizando timestamp
 * Alison - 06/06/10
 */

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

import catalogo.ArquivoCatalogo;
import catalogo.Catalogo;

import p2p.SyncFileApp;

import rice.environment.Environment;
import rice.pastry.NodeHandle;
import rice.pastry.NodeIdFactory;
import rice.pastry.PastryNode;
import rice.pastry.PastryNodeFactory;
import rice.pastry.leafset.LeafSet;
import rice.pastry.socket.SocketPastryNodeFactory;
import rice.pastry.standard.RandomNodeIdFactory;

public class FileSync
{
	////////////////////////////////////////////
	// FIELDS

	// relacionado aos arquivos
	private File rootDir;
	// catálogo
	private Catalogo catalogo;
	// rede p2p
	private SyncFileApp app;
	private PastryNode node;

	///////////////////////////////////////////
	// Getters and Setters
	public File getRootDir() 
	{
		return rootDir;
	}

	/**
	 * Construtor - Inicia o catálogo e entra na rede p2p
	 * @param rootDir
	 * @param bindport
	 * @param bootaddress
	 * @param env
	 * @throws Exception
	 */
	public FileSync(String rootDir, int bindport, InetSocketAddress bootaddress, Environment env) throws Exception
	{
		super();
		this.rootDir =  new File(rootDir);

		// filtro de arquivos
		OnlyFileFilter filtro = new OnlyFileFilter();

		// Cria o catalogo
		this.catalogo = new Catalogo(rootDir);

		// Entra na rede p2p
		this.joinP2P(bindport, bootaddress, env);

		// loop principal do programa
		while (true)
		{		
			env.getTimeSource().sleep(5000);	
			// Cria uma lista só com arquivos locais (sem diretórios)	
			File files[] = this.rootDir.listFiles(filtro);
			
			// busca a diferença do diretório com o catálogo (arquivos novos ou modificados)
			Vector<ArquivoCatalogo> arqsDiffs = catalogo.diffFiles(files);
			
			// para cada arquivo novo ou modificado, solicita um novo timestamp 
			for (Enumeration<ArquivoCatalogo> arqs = arqsDiffs.elements() ; arqs.hasMoreElements();)
			{
				ArquivoCatalogo arq = arqs.nextElement();
				if (arq.getStatus() == Catalogo.STATUS_REQUEST)
				{
					System.out.println("Solicitando um novo timestamp para "+arq.getNome());
					app.routeGetTs(arq.getNome());
				}
			}
			// Espera cinco segundos antes de enviar o catalogo			
			this.sendMyCatalog();
		}
	}

	public void joinP2P(int bindport, InetSocketAddress bootaddress, Environment env) throws Exception
	{
		System.out.println("Iniciando a rede p2p");
		// Gerando o nodeID
		NodeIdFactory nidFactory = new RandomNodeIdFactory(env);
		PastryNodeFactory factory = new SocketPastryNodeFactory(nidFactory, bindport, env);

		// construindo o nodo
		this.node = factory.newNode();
		// constroi a app
		this.app = new SyncFileApp(this.node, env, this.catalogo);
		this.node.boot(bootaddress);

		// the node may require sending several messages to fully boot into the ring
	    synchronized(this.node) {
	      while(!this.node.isReady() && !this.node.joinFailed()) {
	        // delay so we don't busy-wait
	        this.node.wait(500);

	        // abort if can't join
	        if (this.node.joinFailed()) {
	          throw new IOException("Não foi possível entrar na rede p2p. Motivo:"+node.joinFailedReason()); 
	        }
	      }
	    }
	    System.out.println("Finished creating new node "+node);
	}

	public void sendMyCatalog()
	{	
		LeafSet leafSet = node.getLeafSet();
		Set<NodeHandle> sent = new HashSet<NodeHandle>(); 
		for (int i=-leafSet.ccwSize(); i<=leafSet.cwSize(); i++) 
		{
			if (i != 0) // don't send to self 
			{ 
				// select the item
				NodeHandle nh = leafSet.get(i);

				if (!sent.contains(nh)) // send once to neighbour nodes
				{
					// send the message directly to the node
					System.out.println("Enviando o catalogo.");
					app.routeCatalogDirect(nh, catalogo);
					sent.add(nh);
				}
			}
		}
	}
}
